在現代應用服務架構中,保持服務健康不中斷已經是基本要求。傳統的虛擬機(VM)環境需要手動建構額外且複雜的檢查流程來維持服務穩定性。在 Docker 中,雖然可以使用健康檢查機制來監控容器狀態,但其功能相對簡單,缺乏足夠的靈活性和深度。而在 Kubernetes 中,通過使用 Probe,可以提供更自動化且靈活的健康檢查機制,確保應用程序的高可用性和穩定性。
在 Kubernetes 中,Probe
是用來檢查容器健康狀況的機制。它定期檢查容器的狀態,以確保它們正常運行。這些檢查幫助 Kubernetes 採取適當的行動,例如重新啟動失敗的容器或從服務中移除不健康的容器。
Probe 的主要目的是確保應用在 Kubernetes 中的高可用性和穩定性。通過 Probe,可以達成以下目標:
Kubernetes 提供了三種類型的 Probe 來進行健康檢查:
以下是一個 Pod 使用三種類型 Probe 的組態檔範例:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: myapp-container
image: myapp:latest
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
startupProbe:
httpGet:
path: /startup
port: 8080
failureThreshold: 30
periodSeconds: 10
Liveness Probe:
/healthz
路徑的 HTTP GET 請求返回狀態。如果連續 3 次檢查失敗,容器將被重啟。Readiness Probe:
/ready
路徑的 HTTP GET 請求返回狀態。如果檢查失敗,容器會從服務負載均衡器中移除。Startup Probe:
/startup
路徑的 HTTP GET 請求返回狀態。如果超過 failureThreshold
(這裡是 30 次)且仍然失敗,Kubernetes 將認為容器啟動失敗並重啟該容器。組態檔案: pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: test-pod
name: test-pod
spec:
containers:
- name: liveness
command:
- /bin/sh
- -c
args:
- "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 3600"
image: registry.k8s.io/busybox
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
kubectl get events --field-selector involvedObject.name=test-pod -w
LAST SEEN TYPE REASON OBJECT MESSAGE
15s Normal Created pod/test-pod Created container liveness
15s Normal Started pod/test-pod Started container liveness
46s Warning Unhealthy pod/test-pod Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
46s Normal Killing pod/test-pod Container liveness failed liveness probe, will be restarted
15s Normal Pulled pod/test-pod Successfully pulled image "registry.k8s.io/busybox" in 768ms (768ms including waiting). Image size: 1144547 bytes.
可以看到,當 Liveness Probe 偵測到失敗後, Pod 重新啟動。
我們來看看 lofairy/foo
映像檔的 main.go
檔案內容
main.go
package main
import (
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
)
func main() {
hostname, _ := os.Hostname()
started := time.Now()
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "foo")
})
r.GET("/hostname", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"hostname": hostname,
})
})
r.GET("/healthy", func(c *gin.Context) {
c.Status(http.StatusOK)
})
r.GET("/healthy_test", func(c *gin.Context) {
duration := time.Since(started)
if duration.Seconds() > 30 {
c.Status(http.StatusServiceUnavailable)
} else {
c.Status(http.StatusOK)
}
})
r.Run()
}
lofairy/foo
這個 API Server 提供了 /healthy_test
作為探針測試端點,啟動後 30 秒內返回 HTTP 200,之後返回 HTTP 503。我們將使用這個 API 端點進行測試。
組態檔案: pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: test-pod
name: test-pod
spec:
containers:
- name: liveness
image: lofairy/foo
livenessProbe:
httpGet:
path: /healthy_test
port: 8080
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
test-pod
事件kubectl get events --field-selector involvedObject.name=test-pod -w
LAST SEEN TYPE REASON OBJECT MESSAGE
0s Normal Scheduled pod/test-pod Successfully assigned default/test-pod to wslkind-worker2
0s Normal Pulling pod/test-pod Pulling image "lofairy/foo"
0s Normal Pulled pod/test-pod Successfully pulled image "lofairy/foo" in 1.623s (1.623s including waiting). Image size: 9539405 bytes.
0s Normal Created pod/test-pod Created container liveness
0s Normal Started pod/test-pod Started container liveness
0s Warning Unhealthy pod/test-pod Liveness probe failed: HTTP probe failed with statuscode: 503
0s Warning Unhealthy pod/test-pod Liveness probe failed: HTTP probe failed with statuscode: 503
0s Warning Unhealthy pod/test-pod Liveness probe failed: HTTP probe failed with statuscode: 503
0s Normal Killing pod/test-pod Container liveness failed liveness probe, will be restarted
0s Normal Pulling pod/test-pod Pulling image "lofairy/foo"
0s Normal Pulled pod/test-pod Successfully pulled image "lofairy/foo" in 1.544s (1.544s including waiting). Image size: 9539405 bytes.
可以看到,當 Liveness Probe 偵測到失敗後, Pod 重新啟動。
Readiness 的組態參數跟 Liveness 基本一樣,只是將頂級標籤 Liveness
換成了 Readiness
。
組態檔案: pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: test-pod
name: test-pod
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
labels:
type: test-pod-service
name: test-pod-service
spec:
type: NodePort
selector:
app: test-pod
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
就緒探針配置:
httpGet
: 指定通過 HTTP GET 方式探測就緒狀態,請求路徑為 /
,埠為 80。initialDelaySeconds
: 在容器啟動後等待 5 秒才開始進行就緒探針。periodSeconds
: 每 10 秒執行一次就緒探針。failureThreshold
: 如果連續 3 次探測失敗,則認為容器不就緒。簡單來說,我們預期 pod 啟動後 15 秒,就緒態針才會回報成功。
kubectl apply -f pod.yaml
組態檔案: service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
type: test-pod-service
name: test-pod-service
spec:
type: NodePort
selector:
app: test-pod
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
kubectl apply -f service.yaml
curl -o /dev/null -s -w "%{http_code}\n" 0.0.0.0:30000
---
000
我們發現 HTTP Code 回傳 000
,意思是流量根本沒到伺服器,因為就緒態探針沒有通過,叢集就不會讓流量流入該 Pod。
curl -o /dev/null -s -w "%{http_code}\n" 0.0.0.0:30000
---
200
這次回傳 200,因為就緒探針通過了,所以導通了流量。
我們要來驗證,Startup Probe
會組塞 Liveness Probe
或 Readiness Probe
。
組態檔案: pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: test-pod
name: test-pod
spec:
containers:
- name: liveness
command:
- /bin/sh
- -c
args:
- "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 30; touch /tmp/ready; sleep 3600"
image: registry.k8s.io/busybox
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
failureThreshold: 1
periodSeconds: 1
startupProbe:
exec:
command:
- cat
- /tmp/ready
failureThreshold: 10
periodSeconds: 10
說明:
container 啟動時添加檔案 /tmp/healthy
讓 liveness probe 可以成功
container 啟動後 30 秒移除檔案 touch /tmp/healthy
讓 liveness probe 成功
container 啟動後 60 秒添加檔案 touch /tmp/ready
讓 startup probe 成功
建立 Pod
kubectl apply -f pod.yaml
kubectl get events
取得的 probe 事件,只有 Unhealthy
。從上面學習到的知識,我們可以假設接下來的發展:
liveness probe
只會在 startup probe
就緒後動作,並且 liveness probe
的 initialDelaySeconds 設定 10 秒,因此 liveness probe 最後一次報警的時間與 container 啟動時間會相差約 70 秒。我們來驗證一下。
kubectl get events --field-selector involvedObject.name=test-pod -w
LAST SEEN TYPE REASON OBJECT MESSAGE
74s Normal Scheduled pod/test-pod Successfully assigned default/test-pod to wslkind-worker
74s Normal Pulling pod/test-pod Pulling image "registry.k8s.io/busybox"
73s Normal Pulled pod/test-pod Successfully pulled image "registry.k8s.io/busybox" in 768ms (768ms including waiting). Image size: 1144547 bytes.
73s Normal Created pod/test-pod Created container liveness
73s Normal Started pod/test-pod Started container liveness
14s Warning Unhealthy pod/test-pod Startup probe failed: cat: can't open '/tmp/ready': No such file or directory
3s Warning Unhealthy pod/test-pod Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
3s Normal Killing pod/test-pod Container liveness failed liveness probe, will be restarted
可以看到,事件的發展大致跟我們的假設相符。
Probe 中有很多精確和詳細的組態,能精準的控制 liveness 和 readiness 檢查:
initialDelaySeconds
:容器啟動後第一次執行探測是需要等待多少秒。periodSeconds
:執行探測的頻率。默認是 10 秒,最小 1 秒。timeoutSeconds
:探測超時時間。默認 1 秒,最小 1 秒。successThreshold
:探測失敗後,最少連續探測成功多少次才被認定為成功。默認是 1。對於 liveness 必須是 1。最小值是 1。failureThreshold
:探測成功後,最少連續探測失敗多少次才被認定為失敗。默認是 3。最小值是 1。HTTP probe 中可以給 httpGet
設定其他組態項:
host
:連接的主機名,默認連接到 pod 的 IP。你可能想在 http header 中設定 "Host" 而不是使用 IP。scheme
:連接使用的 schema,默認 HTTP。path
: 訪問的 HTTP server 的 path。httpHeaders
:自訂請求的 header。HTTP 運行重複的 header。port
:訪問的容器的連接埠名字或者連接埠號。連接埠號必須介於 1 和 65535 之間。Kubernetes 中的 Probe 是保證容器應用健康和穩定運行的關鍵機制。通過 Liveness、Readiness 和 Startup Probe,Kubernetes 能夠自動監控應用的狀態,並在必要時採取措施恢復服務,從而提升系統的自動化運維能力和應用的可靠性。